#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include "luster_alloc.hh"
#include "luster_build.hh"
#include "luster_c.hh"
#include "luster_fs.hh"
#include "luster_lexpr.hh"
#include "luster_log.hh"
#include "luster_parser.hh"
#include "luster_status.hh"
#include "luster_string.hh"
#include "luster_test.hh"

using luster::Status;
using luster::STATUS_EOF;
using luster::STATUS_ERROR;
using luster::STATUS_OK;

static bool read_line(char *buf, size_t bufsz) {
	fprintf(stdout, "luster> ");
	fflush(stdout);

	if (fgets(buf, bufsz, stdin) == NULL) {
		return false;
	}
	return true;
}

static Status parse_lexpr_each(
	luster::Parser *parser, luster::translator_t *translator) {
	Status status;
	luster::Lexpr lexpr;
	luster::citem_t citem;

	while (true) {
		status = parse_lexpr(parser, &lexpr);
		if (status == STATUS_EOF) {
			return STATUS_OK;
		}

		if (status != STATUS_OK) {
			return status;
		}

		printf("=> ");
		lexpr_display(&lexpr, stdout);
		printf("\n");

		status = translate(translator, &lexpr, &citem);
		if (status != STATUS_OK) {
			return status;
		}

		printf("c> ");
		citem_display(&citem, stdout, .need_bracket = false);
		printf("\n");

		lexpr_deinit(&lexpr);
	}
}

static void display_error(char const *text, uintptr_t err_pos, Status status) {

	uintptr_t start_pos = err_pos;

	while (start_pos > 0) {
		if (text[start_pos - 1] == '\n') {
			break;
		}
		start_pos -= 1;
	}

	uintptr_t end_pos = err_pos;

	while (text[end_pos] != 0) {
		if (text[end_pos + 1] == '\n') {
			break;
		}
		end_pos += 1;
	}

	fputs("| ", stdout);
	for (uintptr_t i = start_pos; i <= end_pos; ++i) {
		fputc(text[i], stdout);
	}
	fputc('\n', stdout);

	fputs("| ", stdout);
	for (uintptr_t i = start_pos; i <= end_pos; ++i) {
		if (i < err_pos) {
			fputc(' ', stdout);
		} else if (i == err_pos) {

			fputc('^', stdout);
		} else {
			break;
		}
	}
	fputc('\n', stdout);

	printf("error: %s\n", status_description(status));
}

static char const *g_prog = NULL;
static char const **g_cmd_args = NULL;
static uintptr_t g_cmd_argc = 0;

Status cmd_help(void) {
	fprintf(stderr, "Usage: %s <cmd> <args>...\n", g_prog);
	fprintf(stderr, "\n");
	fprintf(stderr, "Commands:\n");
	fprintf(stderr, "  h|help         Print this help and exit\n");
	fprintf(stderr, "  i|interactive  Start interactive shell\n");
	fprintf(stderr, "  t|translate <file>\n");
	fprintf(stderr, "                 Translate a file to C\n");
	fprintf(stderr, "  test           Run unit tests\n");
	return STATUS_OK;
}

static Status cmd_interactive(void) {
	luster::Parser parser;
	Status status;
	luster::translator_t translator;
	char buf[1024];

	while (read_line(buf, sizeof(buf))) {
		parser.text = buf;
		parser.curr = 0;
		parser.err_pos = 0;

		status = parse_lexpr_each(&parser, &translator);
		if (status == STATUS_OK) {
			continue;
		}
		display_error(parser.text, parser.err_pos, status);
	}

	return STATUS_OK;
}

static Status CmdTest(void) {
	luster::luster_cmd_test();
	return STATUS_OK;
}

static Status CmdTranslate(void) {
	if (g_cmd_argc != 2) {
		logprint(LOG_ERROR,
			"translate: expect arguments <luster_file> and "
			"<c_file>");
		return STATUS_OK;
	}

	char const *path = g_cmd_args[0];
	char const *cpath = g_cmd_args[1];

	logprint(LOG_INFO, "translate: %s -> %s", path, cpath);

	luster::StringBuffer lus_code;
	luster_OWN(lus_code);
	Status status = luster::read_file(path, &lus_code);

	if (status != STATUS_OK) {
		return status;
	}

	luster::Parser parser = {.text = stringbuf_get(&lus_code)};
	luster::translator_t translator = {0};

	luster::Lexpr lexpr;
	luster::citem_t citem;

	luster::FilePtr c_fp;
	luster_OWN(c_fp);

	c_fp.fp = fopen(cpath, "w");

	while (true) {
		status = parse_lexpr(&parser, &lexpr);
		if (status == STATUS_EOF) {
			break;
		}

		if (status != STATUS_OK) {
			return status;
		}

		fprintf(c_fp.fp, "/* ");
		lexpr_display(&lexpr, c_fp.fp);
		fprintf(c_fp.fp, " */\n");

		status = translate(&translator, &lexpr, &citem);
		if (status != STATUS_OK) {
			return status;
		}

		citem_display(&citem, c_fp.fp, .need_bracket = false);
		fprintf(c_fp.fp, "\n");

		lexpr_deinit(&lexpr);
		citem_deinit(&citem);
	}

	return STATUS_OK;
}

typedef Status (*St0Cmd)(void);

struct NamedCmd {
	char const *name;
	char const *name_alias;
	St0Cmd cmd;
};

int main(int argc, char const **argv) {
	char const *cmd_name = "help";

	g_prog = argv[0];
	g_cmd_args = argv + 1;
	g_cmd_argc = argc - 1;

	if (argc > 1) {
		cmd_name = argv[1];
		g_cmd_args += 1;
		g_cmd_argc -= 1;
	}

	NamedCmd named_cmds[] = {
		{
			.name = "help",
			.name_alias = "h",
			.cmd = cmd_help,
		},
		{
			.name = "interactive",
			.name_alias = "i",
			.cmd = cmd_interactive,
		},
		{
			.name = "translate",
			.name_alias = "t",
			.cmd = CmdTranslate,
		},
		{
			.name = "test",
			.name_alias = NULL,
			.cmd = CmdTest,
		},
		{
			.name = "build",
			.name_alias = NULL,
			.cmd = luster::luster_cmd_build,
		},
		{
			.name = NULL,
		},
	};

	St0Cmd cmd_func = NULL;
	for (uintptr_t i = 0; named_cmds[i].name != NULL; ++i) {
		NamedCmd *named_cmd = named_cmds + i;

		/* cmd_name equals to a name or a alias of name. */
		if (strcmp(cmd_name, named_cmd->name) == 0 ||
			(named_cmd->name_alias != NULL &&
				strcmp(cmd_name, named_cmd->name_alias) == 0)) {
			cmd_func = named_cmd->cmd;
		}
	}

	if (cmd_func == NULL) {
		logprint(LOG_ERROR, "unexpected command: %s", cmd_name);
		return STATUS_ERROR;
	}

	Status status = cmd_func();
	if (status != STATUS_OK) {
		logprint(LOG_ERROR, "%s: %s", cmd_name,
			status_description(status));
		return 1;
	}

	luster::check_memleak();

	return 0;
}
